class ProxyObserver {
  
  constructor() {
    /**
     * event queue
     * {
     *   "event": [],
     *   "event2": [],
     *   ...
     * }
     * 
     * proxy queue
     * {
     *   proxy1: "event",
     *   proxy2: "event2",
     *   ...
     * }
     * 
     * cache
     * {
     *   "event": [...args, ...args2],
     *   "event2": [...args3, ...args4]
     * }
     */
    this._eventQueue = {};
    this._proxyQueue = {};

    // TODO: for a published event before being subscribed
    this._cache = {};
  }
  
  _checkQueue(target, tag, args) {
    this[target][tag]
    ? this[target][tag].push(args)
    : ((this[target][tag] = []) && (this[target][tag].push(args)));
  }

  _loadCache(tag, callback) {
    if (this._cache[tag]) {
      for (let args of this._cache[tag]) {
        callback(...args);
      }
    }
  }

  _clearCompleteCache(tag) {

    // console.log(`>>> before clear => ${JSON.stringify(this._cache)}`);
    delete this._cache[tag];
    // console.log(`>>> after clear => ${JSON.stringify(this._cache)}`);
  }

  createObserver(tag) {
    if (typeof tag !== 'string') {
      throw new Error(`<ReferenceError>: '${tag}' is not string, and cannot be used as a tag.`);
    }

    return new Promise((resolve, reject) => {
      try {

        // push callback in line
        this._checkQueue('_eventQueue', tag, resolve);

        if (this._cache[tag]) {
          this._loadCache(tag, resolve);
          this._clearCompleteCache(tag);
        }

      } catch (err) {

        // TODO: error message?
        reject(err);
      }
    });
  }

  refreshEvent(tag, ...args) {

    // if there is nobody subscribe the event, save it for next subscribe
    if (!this._eventQueue[tag]) {
      this._checkQueue('_cache', tag, args);
      console.warn(`<Warning>: '${tag}' is not defined yet. Thus, we save it and trigger it for your next subscription`);
      return this;
    }

    // map and exec
    for (let eachCall of this._eventQueue[tag]) {
      // console.log(eachCall, ...args);
      // FIXME: cannot call the function 
      eachCall(...args);
    }

    return this;

  }

  // trigger
  createProxy(tag, proxyTarget) {
    let proxy;

    if( !proxyTarget || typeof proxyTarget !== 'object' || proxyTarget instanceof Number || proxyTarget instanceof String) {
      new Error(`ReferenceError: '${proxyTarget}' is not a normal object.`);
    }

    proxy = new Proxy(proxyTarget, {

      set: () => {
        console.log(`set => `);
      },

      get: () => {
        this.count++;
        console.log(`get => `, this.count);
      }

    });

    return proxy;
  }

  list() {
    console.log(this._eventQueue);
  }
}

let icy = new ProxyObserver();

module.exports = {
  createObserver: icy.createObserver,
  createProxy: icy.createProxy,
  refreshEvent: icy.refreshEvent,
  list: icy.list
};
